home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / oscar / OscarBuddies.pyo (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-13  |  27KB  |  823 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. from __future__ import with_statement
  5. aim5_caps = frozenset(('buddy_list_transfer', 'chat_service', 'voice_chat', 'file_xfer', 'direct_im', 'avatar', 'add_ins'))
  6. digsby = frozenset(('icq_to_aim', 'buddy_list_transfer', 'ichatav_info', 'xhtml_support', 'avatar', 'digsby'))
  7. pidgin_caps = frozenset(('file_xfer', 'direct_im', 'avatar', 'chat_service'))
  8. aim6_caps = frozenset(('direct_im', 'chat_service', 'file_xfer', 'voice_chat', 'avatar'))
  9. icq6_caps = frozenset(('icq6_unknown5', 'icq_unknown1', "\xe3b\xc1\xe9\x12\x1aK\x94\xa6&zt\xde$'\r", 'aim_file_xfer', 'icq6_unknown1', 'rtf_support', 'icq6_unknown3', 'route_finder', '\xb9\x97\x08\xb5:\x92B\x02\xb0i\xf1\xe7W\xbb.\x17', 'icq6_unknown4'))
  10. clients_supporting_html = frozenset(('aim60', 'aim59', 'purple', 'icq6', 'mobile', 'digsby', 'miranda-aim'))
  11. import struct
  12. import time
  13. import datetime
  14. from util.observe import ObservableDict, ObservableProperty
  15. oproperty = ObservableProperty
  16. import common
  17. from common import profile
  18. from common.actions import action
  19. from logging import getLogger
  20. log = getLogger('oscar.buddies')
  21. import util
  22. import oscar
  23. from util import get
  24. from util import Storage
  25. from util import cproperty
  26. from util import FilterDict
  27. from util import preserve_whitespace
  28. from util.callbacks import callsback
  29. from traceback import print_exc
  30. import re
  31. body_pattern = re.compile('<(body|BODY).*?((bgcolor|BGCOLOR)=(.*?))? ?>')
  32. blast_group_re = re.compile('\\[.*\\]')
  33. import oscar.capabilities as oscar
  34. statuses = Storage({
  35.     'free for chat': _('Free for Chat'),
  36.     'busy': _('Occupied') })
  37. special_msg_strings = {
  38.     '%n': (lambda buddy: buddy.protocol.username),
  39.     '%d': (lambda buddy: datetime.date.today().strftime('%m/%d/%Y')),
  40.     '%t': (lambda buddy: datetime.datetime.now().strftime('%I:%M:%S %p')) }
  41.  
  42. def magic_string_repl(s, buddy):
  43.     for key, substitute_func in special_msg_strings.iteritems():
  44.         if key in s:
  45.             s = s.replace(key, substitute_func(buddy))
  46.             continue
  47.     
  48.     return s
  49.  
  50. IGNORE_CAPABILITIES = False
  51.  
  52. def sanitize_aim_html(html):
  53.     (html, bgcolor) = _remove_html_cruft(html)
  54.     return html
  55.  
  56.  
  57. def _remove_html_cruft(s):
  58.     lower = s.lower()
  59.     s = None if lower.startswith('<html>') else s
  60.     s = None if lower.endswith('</html>') else s
  61.     match = body_pattern.search(s)
  62.     bgcolor = u''
  63.     while match:
  64.         s = s[:match.start()] + s[match.end():]
  65.         groups = match.groups()
  66.         if groups[-1]:
  67.             bgcolor = u'bgcolor=' + groups[-1] + u' '
  68.         
  69.         match = body_pattern.search(s)
  70.     s = s.replace('</body>', '').replace('</BODY>', '').replace('<br/>', '<br />')
  71.     
  72.     try:
  73.         return (s.decode('utf-8', 'ignore'), bgcolor)
  74.     except Exception:
  75.         return (unicode(s), bgcolor)
  76.  
  77.  
  78.  
  79. def aim_to_wxhtml(s):
  80.     (s, bgcolor) = _remove_html_cruft(s)
  81.     return u'<TABLE width=100%% %scellpadding=0 cellspacing=0 border=0><tr><td>%s</td></tr></table>' % (bgcolor, preserve_whitespace(s))
  82.  
  83.  
  84. def make_dgetter(obj):
  85.     
  86.     def dgetter(key):
  87.         val = get(obj, key, u'')
  88.         if not val:
  89.             val = u''
  90.         
  91.         if not isinstance(val, unicode):
  92.             return val.decode('fuzzy utf8')
  93.         
  94.         return val
  95.  
  96.     return dgetter
  97.  
  98.  
  99. def make_pretty_addr(d):
  100.     addrkeys = ('street', 'city', 'state', 'zip', 'country')
  101.     get = make_dgetter(d)
  102.     
  103.     def addytemplate(d):
  104.         fields = []
  105.         for k in addrkeys:
  106.             fields.append(get(k))
  107.         
  108.         return 'http://maps.google.com/maps?q=' + u' '.join(filter(None, fields)).encode('utf-8').encode('url')
  109.  
  110.     country = get('country')
  111.     zip = get('zip')
  112.     state = get('state')
  113.     city = get('city')
  114.     street = get('street')
  115.     res = []
  116.     
  117.     add = lambda s: res.insert(0, s)
  118.     if country:
  119.         add(u'\n' + country)
  120.     
  121.     if zip:
  122.         add(zip)
  123.     
  124.     if state:
  125.         if zip:
  126.             add(u', ')
  127.         
  128.         add(state)
  129.     
  130.     if city:
  131.         if state or zip:
  132.             add(u', ')
  133.         
  134.         add(city)
  135.     
  136.     if street:
  137.         if city and state or zip:
  138.             add(u'\n')
  139.         
  140.         add(street)
  141.     
  142.     if res:
  143.         add([
  144.             u'(',
  145.             (addytemplate(d), u'map'),
  146.             u')\n'])
  147.     
  148.     return res
  149.  
  150.  
  151. class OscarBuddy(common.buddy):
  152.     
  153.     def __init__(self, name, protocol):
  154.         self._status = 'unknown'
  155.         self._idle_time = None
  156.         self._friendly_name = None
  157.         self._avail_msg = None
  158.         self._away_msg = None
  159.         self._user_class = self.create_time = self.signon_time = 0
  160.         common.buddy.__init__(self, name.lower().replace(' ', ''), protocol)
  161.         self.account_creation_time = None
  162.         self.online_time = None
  163.         self.user_status_icq = 'offline'
  164.         self.external_ip_icq = 0
  165.         self._dc_info = util.Storage()
  166.         self._capabilities = []
  167.         self.userinfo = { }
  168.         if self._profile is False:
  169.             self._profile = None
  170.         
  171.         self._away_updated = self._mystery_updated = 0
  172.         self._waiting_for_presence = True
  173.  
  174.     
  175.     def __hash__(self):
  176.         return common.buddy.__hash__(self)
  177.  
  178.     
  179.     def sightly_status(self):
  180.         if self.mobile:
  181.             return _('Mobile')
  182.         elif self.service == 'aim':
  183.             return statuses.get(self.status, self.status.title())
  184.         else:
  185.             return statuses.get(self._status, self._status.title())
  186.  
  187.     sightly_status = property(sightly_status)
  188.     
  189.     def pretty_profile(self):
  190.         odict = odict
  191.         import util
  192.         if self.service == 'aim':
  193.             prof = [
  194.                 None if self.profile else aim_to_wxhtml(u'No Profile')]
  195.             pref = pref
  196.             import common
  197.             if pref('infobox.aim.show_profile_link', True):
  198.                 linkage = odict()
  199.                 linkage['space'] = 4
  200.                 url = ''.join([
  201.                     'http://www.aimpages.com/',
  202.                     self.name,
  203.                     '/profile.html'])
  204.                 linkage['Profile URL:'] = [
  205.                     '\n',
  206.                     (url, url)]
  207.                 prof += [
  208.                     linkage]
  209.             
  210.             return prof
  211.         else:
  212.             ok = False
  213.             profile = odict()
  214.             bget = make_dgetter(self)
  215.             if bget('first') or bget('last'):
  216.                 profile['Full Name:'] = ' '.join([
  217.                     self.first,
  218.                     self.last])
  219.                 ok = True
  220.             
  221.             personal = getattr(self, 'personal', { })
  222.             if personal:
  223.                 pget = make_dgetter(personal)
  224.                 homeaddr = make_pretty_addr(personal)
  225.             else:
  226.                 
  227.                 pget = lambda s: ''
  228.                 homeaddr = ''
  229.             for key in 'gender birthday'.split():
  230.                 if hasattr(personal, key) and getattr(personal, key, False):
  231.                     profile[key.capitalize() + ':'] = pget(key)
  232.                     ok = True
  233.                     continue
  234.             
  235.             p_web = pget('website')
  236.             if p_web:
  237.                 profile['Website:'] = (p_web, p_web)
  238.                 ok = True
  239.             
  240.             prof = bget('profile')
  241.             if prof:
  242.                 if ok:
  243.                     profile['sep1'] = 4
  244.                 
  245.                 
  246.                 try:
  247.                     profstr = u'\n' + prof
  248.                 except Exception:
  249.                     pass
  250.  
  251.                 profile['Additional Information:'] = profstr
  252.                 ok = True
  253.             
  254.             if homeaddr:
  255.                 if ok:
  256.                     profile['sep2'] = 4
  257.                 
  258.                 profile['Home Address:'] = homeaddr
  259.                 ok = True
  260.             
  261.             work = getattr(self, 'work', { })
  262.             if work:
  263.                 wget = make_dgetter(work)
  264.                 workaddr = make_pretty_addr(work)
  265.             else:
  266.                 
  267.                 wget = lambda s: ''
  268.                 workaddr = ''
  269.             if workaddr:
  270.                 if ok:
  271.                     profile['sep3'] = 4
  272.                 
  273.                 profile['Work Address:'] = workaddr
  274.                 ok = True
  275.             
  276.             if ok:
  277.                 profile['sep4'] = 4
  278.                 ok = False
  279.             
  280.             for key in 'company department position'.split():
  281.                 if hasattr(work, key) and getattr(work, key, False):
  282.                     profile[key.capitalize() + ':'] = wget(key)
  283.                     ok = True
  284.                     continue
  285.             
  286.             w_web = wget('website')
  287.             if w_web:
  288.                 profile['Work Website:'] = (w_web, w_web)
  289.                 ok = True
  290.             
  291.             if ok:
  292.                 ok = False
  293.                 profile['sep5'] = 4
  294.             
  295.             url = ''.join([
  296.                 u'http://people.icq.com/people/about_me.php?uin=',
  297.                 self.name])
  298.             profile['Profile URL:'] = [
  299.                 '\n',
  300.                 (url, url)]
  301.             return profile
  302.  
  303.     pretty_profile = property(pretty_profile)
  304.     
  305.     def service(self):
  306.         return None if self.name.isdigit() else 'aim'
  307.  
  308.     service = property(service)
  309.     
  310.     def icq(self):
  311.         return self.service == 'icq'
  312.  
  313.     icq = property(icq)
  314.     
  315.     def update(self, userinfo):
  316.         self._idle_time = None
  317.         notifyattrs = []
  318.         notify_append = notifyattrs.append
  319.         is_self_buddy = self.protocol.self_buddy is self
  320.         self.frozen().__enter__()
  321.         
  322.         try:
  323.             for k, v in userinfo.iteritems():
  324.                 if isinstance(k, basestring):
  325.                     if k == 'status':
  326.                         if is_self_buddy:
  327.                             continue
  328.                         
  329.                         if v == 'online':
  330.                             v = 'available'
  331.                         
  332.                         self.status = v
  333.                         notify_append('status')
  334.                     elif k == 'avail_msg':
  335.                         self._set_avail_msg(v, False)
  336.                         notify_append('status_message')
  337.                     else:
  338.                         
  339.                         try:
  340.                             setattr(self, k, v)
  341.                             notify_append(k)
  342.                         except AttributeError:
  343.                             self.frozen()
  344.                             e = self.frozen()
  345.                             print_exc()
  346.                         except:
  347.                             self.frozen()<EXCEPTION MATCH>AttributeError
  348.                         
  349.  
  350.                 self.frozen()<EXCEPTION MATCH>AttributeError
  351.                 self.userinfo[k] = v
  352.         finally:
  353.             pass
  354.  
  355.         if self._status != 'away':
  356.             self._waiting_for_presence = False
  357.         
  358.         notify = self.notify
  359.         for attr in notifyattrs:
  360.             notify(attr)
  361.         
  362.  
  363.     
  364.     def _get_status_orb(self):
  365.         if self.idle:
  366.             return 'idle'
  367.         elif self.away:
  368.             return 'away'
  369.         else:
  370.             get_status_orb = get_status_orb
  371.             import common.Buddy
  372.             return get_status_orb(self)
  373.  
  374.     status_orb = oproperty(_get_status_orb, observe = 'status')
  375.     
  376.     def get_profile(self):
  377.         p = self._profile
  378.         if p is not None:
  379.             p = magic_string_repl(p, self)
  380.         
  381.         return p
  382.  
  383.     
  384.     def set_profile(self, profile):
  385.         self._profile = profile
  386.  
  387.     profile = property(get_profile, set_profile, doc = "This buddy's AIM profile or ICQ 'about me'.")
  388.     _profile_updated = cproperty(0)
  389.     
  390.     def set_profile_updated(self, netint):
  391.         self._profile_updated = None if isinstance(netint, basestring) else netint
  392.  
  393.     profile_updated = property((lambda b: b._profile_updated), set_profile_updated)
  394.     
  395.     def set_away_updated(self, netint):
  396.         self._away_updated = None if isinstance(netint, basestring) else netint
  397.  
  398.     away_updated = property((lambda b: b._away_updated), set_away_updated)
  399.     
  400.     def set_mystery_updated(self, netint):
  401.         self._mystery_updated = None if isinstance(netint, basestring) else netint
  402.  
  403.     mystery_updated = property((lambda b: b._mystery_updated), set_mystery_updated)
  404.     
  405.     def sms(self):
  406.         return self.name.startswith('+')
  407.  
  408.     sms = property(sms)
  409.     _profile = cproperty(None)
  410.     
  411.     def request_info(self, profile, away):
  412.         if not profile and not away:
  413.             return None
  414.         
  415.         self.protocol.get_buddy_info(self, profile = profile, away = away)
  416.  
  417.     
  418.     def set_nice_name(self, name):
  419.         self._nice_name = name
  420.  
  421.     
  422.     def get_nice_name(self):
  423.         return getattr(self, '_nice_name', self.name)
  424.  
  425.     nice_name = property(get_nice_name, set_nice_name)
  426.     
  427.     def _set_capabilities(self, newval):
  428.         if self is self.protocol.self_buddy:
  429.             return None
  430.         
  431.         caps = []
  432.         while newval:
  433.             (caphi, caplo, newval) = oscar.unpack((('caphi', 'Q'), ('caplo', 'Q')), newval)
  434.             caps.append(struct.pack('!QQ', caphi, caplo))
  435.         if self._capabilities and len(caps) == 1 and caps[0] == oscar.capabilities.by_name['chat_service']:
  436.             log.info('Received dummy capabilities for %r, not setting them', self.name)
  437.             return None
  438.         
  439.         self._capabilities = caps
  440.         log.debug("%r's caps are: %r", self, self.pretty_caps)
  441.  
  442.     capabilities = ObservableProperty((lambda self: self._capabilities), _set_capabilities, observe = '_capabilities')
  443.     
  444.     def pretty_caps(self):
  445.         return map((lambda x: oscar.capabilities.by_bytes.get(x, x)), self._capabilities)
  446.  
  447.     pretty_caps = property(pretty_caps)
  448.     
  449.     def caps(self):
  450.         digsbycaps = caps
  451.         import common
  452.         import oscar.capabilities as oscarcaps
  453.         protocaps = list(self.protocol.caps)
  454.         mycaps = [ oscarcaps.by_bytes.get(x, None) for x in self.capabilities ]
  455.         if _lowerstrip(self.name) in self.protocol.bots or blast_group_re.match(_lowerstrip(self.name)):
  456.             protocaps.append(digsbycaps.BOT)
  457.         
  458.         return protocaps
  459.  
  460.     caps = property(caps)
  461.     
  462.     def supports_html_messages(self):
  463.         client = self.guess_client()
  464.         if client == 'purple' and self.protocol.self_buddy.icq:
  465.             return False
  466.         
  467.         caps = set(self.pretty_caps)
  468.         if 'xhtml_support' in caps:
  469.             return True
  470.         
  471.         if 'rtf_support' in caps:
  472.             return False
  473.         
  474.         return client in clients_supporting_html
  475.  
  476.     supports_html_messages = property(supports_html_messages)
  477.     
  478.     def guess_client(self):
  479.         caps = set(self.pretty_caps)
  480.         if caps.issuperset(icq6_caps):
  481.             return 'icq6'
  482.         elif any((lambda .0: for x in .0:
  483. x.startswith('Miranda'))(caps)):
  484.             if caps.issuperset(('icq_unknown1', 'icq6_unknown4')):
  485.                 return 'miranda-icq'
  486.             else:
  487.                 return 'miranda-aim'
  488.         elif caps.issuperset(aim5_caps):
  489.             return 'aim59'
  490.         elif 'digsby' in caps:
  491.             return 'digsby'
  492.         elif caps.issuperset(aim6_caps) or 'aim6_unknown1' in caps:
  493.             return 'aim60'
  494.         elif caps.issuperset(pidgin_caps):
  495.             return 'purple'
  496.         elif self.mobile or self.sms:
  497.             return 'mobile'
  498.         else:
  499.             return 'unknown'
  500.  
  501.     
  502.     def _set_dc_info(self, newval):
  503.         (dc_info, data) = oscar.unpack((('_', 'dc_info'),), newval)
  504.         if data:
  505.             print 'extra data on dc_info for %s: %s' % (self.name, util.to_hex(data))
  506.         
  507.         self._dc_info = dc_info
  508.  
  509.     dc_info = ObservableProperty((lambda self: self._dc_info), _set_dc_info, observe = '_dc_info')
  510.     
  511.     def _set_user_class(self, newval):
  512.         self._user_class = struct.unpack('!H', newval)[0]
  513.  
  514.     user_class = ObservableProperty((lambda self: self._user_class), _set_user_class, observe = '_user_class')
  515.     
  516.     def _set_avail_msg(self, newval, notify = True):
  517.         old = self._avail_msg
  518.         tflvs = { }
  519.         (tflvs_list, newval) = oscar.unpack((('values', 'tflv_list'),), newval)
  520.         for tflv in tflvs_list:
  521.             tflvs[tflv.t] = tflv.v
  522.         
  523.         if 1 in tflvs:
  524.             self.setnotifyif('icon_hash', tflvs[1])
  525.         
  526.         if 2 in tflvs:
  527.             if len(tflvs[2]) > 0:
  528.                 fmt = (('msglen', 'H'), ('msg', 's', 'msglen'), ('numtlvs', 'H'), ('tlvs', 'tlv_list', 'numtlvs'))
  529.                 (__, msg, __, tlvs, tflvs[2]) = oscar.unpack(fmt, tflvs[2])
  530.                 if self is self.protocol.self_buddy:
  531.                     return None
  532.                 
  533.                 codecs = [
  534.                     'fuzzy',
  535.                     'utf-8']
  536.                 self._avail_msg = msg.decode(' '.join(codecs))
  537.                 self._cached_status_message = None
  538.             else:
  539.                 self._avail_msg = None
  540.         else:
  541.             notify = False
  542.         if notify:
  543.             self.notify('avail_msg', old, self._avail_msg)
  544.         
  545.  
  546.     
  547.     def _get_avail_msg(self):
  548.         return self._avail_msg
  549.  
  550.     avail_msg = property(_get_avail_msg, _set_avail_msg)
  551.     
  552.     def __repr__(self):
  553.         return '<OscarBuddy %s>' % _lowerstrip(self.name)
  554.  
  555.     
  556.     def get_idle(self):
  557.         return self._idle_time
  558.  
  559.     
  560.     def set_idle(self, val):
  561.         self._idle_time = val
  562.  
  563.     idle = ObservableProperty(get_idle, set_idle, observe = '_idle_time')
  564.     
  565.     def set_idle_time(self, netidle):
  566.         if isinstance(netidle, basestring):
  567.             self._idle_time = int(time.time() - 60 * struct.unpack('!H', netidle)[0])
  568.         elif isinstance(netidle, int):
  569.             self._idle_time = netidle
  570.         elif netidle is None:
  571.             self._idle_time = None
  572.         else:
  573.             log.warning('set_idle_time received %r', netidle)
  574.  
  575.     
  576.     def get_idle_time(self):
  577.         i = self._idle_time
  578.         return None if i is None else int(i)
  579.  
  580.     idle_time = property(get_idle_time, set_idle_time, None, 'Set by the network.')
  581.     
  582.     def get_status(self):
  583.         if self._status == 'offline':
  584.             if self.mobile:
  585.                 return 'mobile'
  586.             else:
  587.                 return 'offline'
  588.         
  589.         if self._status == 'unknown' or self._waiting_for_presence:
  590.             return 'unknown'
  591.         elif self.away or self._status == 'away':
  592.             return 'away'
  593.         elif self.idle or self._status == 'idle':
  594.             return 'idle'
  595.         else:
  596.             return 'available'
  597.  
  598.     
  599.     def set_status(self, newval):
  600.         self._status = newval
  601.  
  602.     status = ObservableProperty(get_status, set_status, observe = '_status')
  603.     
  604.     def set_away_msg(self, away_msg):
  605.         if not isinstance(away_msg, (basestring, type(None))):
  606.             raise TypeError(str(type(away_msg)))
  607.         
  608.         self._waiting_for_presence = False
  609.         self._away_msg = away_msg
  610.  
  611.     
  612.     def get_away_msg(self):
  613.         return self._away_msg
  614.  
  615.     away_msg = property(get_away_msg, set_away_msg)
  616.     
  617.     def get_online(self):
  618.         if self._status != 'offline':
  619.             pass
  620.         return self._status != 'unknown'
  621.  
  622.     online = ObservableProperty(get_online, observe = '_status')
  623.     
  624.     def _away(self):
  625.         if self.user_class:
  626.             v = bool(self.user_class & 32)
  627.         else:
  628.             v = self._status == 'away'
  629.         if self.online:
  630.             pass
  631.         return v
  632.  
  633.     away = ObservableProperty(_away, observe = 'user_class')
  634.     
  635.     def direct_connect(self):
  636.         return self.protocol.direct_connect(self)
  637.  
  638.     
  639.     def _get_invisible(self):
  640.         return bool(self.user_class & 256)
  641.  
  642.     
  643.     def _set_invisible(self, invis):
  644.         pass
  645.  
  646.     invisible = ObservableProperty(_get_invisible, _set_invisible, observe = 'user_class')
  647.     
  648.     def _get_stripped_msg(self):
  649.         if not self.status_message:
  650.             pass
  651.         msg = util.strip_html2('')
  652.         
  653.         try:
  654.             return msg.decode('xml')
  655.         except Exception:
  656.             return msg
  657.  
  658.  
  659.     stripped_msg = ObservableProperty(_get_stripped_msg, observe = 'status_message')
  660.     
  661.     def get_status_message(self):
  662.         if self.away and self.away_msg and self.away_msg.strip():
  663.             return aim_to_wxhtml(magic_string_repl(self.away_msg, self))
  664.         elif self.avail_msg and self.avail_msg.strip():
  665.             return aim_to_wxhtml(magic_string_repl(self.avail_msg.encode('xml'), self))
  666.         else:
  667.             return None
  668.  
  669.     
  670.     def set_status_message(self, val):
  671.         if not isinstance(val, (basestring, type(None))):
  672.             raise TypeError(str(type(val)))
  673.         
  674.         self.away_msg = self._avail_msg = val
  675.  
  676.     status_message = ObservableProperty(get_status_message, set_status_message, observe = [
  677.         'away_msg',
  678.         '_avail_msg'])
  679.     
  680.     def _mobile(self):
  681.         if self.user_class and self.user_class & 128 != 0:
  682.             pass
  683.         return self.protocol.self_buddy != self
  684.  
  685.     mobile = ObservableProperty(_mobile, observe = 'user_class')
  686.     
  687.     def _set_online_time(self, val):
  688.         if isinstance(val, str) and len(val) == 4:
  689.             self._online_time = time.time() - struct.unpack('!I', val)[0]
  690.         else:
  691.             self._online_time = val
  692.  
  693.     
  694.     def _get_online_time(self):
  695.         return self._online_time
  696.  
  697.     online_time = ObservableProperty(_get_online_time, _set_online_time, observe = '_online_time')
  698.     
  699.     def __str__(self):
  700.         return self.name
  701.  
  702.     
  703.     def __cmp__(self, other):
  704.         if not isinstance(other, self.__class__):
  705.             return -1
  706.         
  707.         a = _lowerstrip(self.name)
  708.         b = _lowerstrip(other.name)
  709.         return cmp(a, b)
  710.  
  711.     
  712.     def get_buddy_icon(self):
  713.         self.protocol.get_buddy_icon(self.name)
  714.  
  715.     
  716.     def blocked(self):
  717.         if not self.protocol.icq:
  718.             return _lowerstrip(self.name) in self.protocol.block_list
  719.         else:
  720.             return _lowerstrip(self.name) in self.protocol.ignore_list
  721.  
  722.     blocked = property(blocked)
  723.     
  724.     def pending_auth(self):
  725.         return False
  726.  
  727.     pending_auth = property(pending_auth)
  728.     
  729.     def get_remote_alias(self):
  730.         a = getattr(self, 'nick', None)
  731.         if not a:
  732.             a = '%s %s' % (getattr(self, 'first', ''), getattr(self, 'last', ''))
  733.             if not a.strip():
  734.                 pass
  735.             a = None
  736.         
  737.         return a
  738.  
  739.     remote_alias = oproperty(get_remote_alias, observe = [
  740.         'first',
  741.         'last',
  742.         'nick'])
  743.     first = cproperty()
  744.     last = cproperty()
  745.     nick = cproperty()
  746.     personal = cproperty()
  747.     work = cproperty()
  748.     
  749.     def get_alias(self):
  750.         val = None
  751.         a = profile.get_contact_info(self, 'alias')
  752.         if a and a.strip():
  753.             val = a
  754.         else:
  755.             for attr in ('local_alias', 'remote_alias', '_friendly_name', 'nice_name'):
  756.                 val = getattr(self, attr, None)
  757.                 if val:
  758.                     break
  759.                     continue
  760.             else:
  761.                 val = self.name
  762.         return val.decode('fuzzy utf-8')
  763.  
  764.     alias = oproperty(get_alias, observe = [
  765.         'local_alias',
  766.         'remote_alias',
  767.         'nice_name'])
  768.     
  769.     def block(self, set_blocked = True, callback = None):
  770.         if not self.protocol.icq:
  771.             return self.protocol.block(self, set_blocked, callback = callback)
  772.         else:
  773.             return self.protocol.ignore(self, set_blocked, callback = callback)
  774.  
  775.     block = callsback(block)
  776.     
  777.     def unblock(self, callback = None):
  778.         return self.block(False, callback = callback)
  779.  
  780.     unblock = callsback(unblock)
  781.     
  782.     def permit(self, set_allowed = True):
  783.         return self.protocol.permit(self, set_allowed)
  784.  
  785.     
  786.     def unpermit(self):
  787.         return self.permit(False)
  788.  
  789.     
  790.     def warn(self, anon = True):
  791.         return self.protocol.warn(self, anon)
  792.  
  793.  
  794.  
  795. class OscarBuddies(ObservableDict, FilterDict):
  796.     
  797.     def __init__(self, protocol):
  798.         ObservableDict.__init__(self)
  799.         FilterDict.__init__(self, _lowerstrip)
  800.         self.protocol = protocol
  801.  
  802.     
  803.     def __getitem__(self, k):
  804.         
  805.         try:
  806.             return FilterDict.__getitem__(self, k)
  807.         except (KeyError,):
  808.             return self.setdefault(self.ff(k), OscarBuddy(k, self.protocol))
  809.  
  810.  
  811.     
  812.     def update_buddies(self, infos):
  813.         for info in infos:
  814.             self[info.name].update(info)
  815.         
  816.  
  817.  
  818.  
  819. def _lowerstrip(name):
  820.     return str(name).lower().replace(' ', '')
  821.  
  822. _lowerstrip = util.memoize(_lowerstrip)
  823.